from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QDir, QDirIterator, QObject, QVariant
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel, QSqlField, QSqlRecord
import hashlib
import os

from RBK.Model.DataType import DataType
from RBK.Model.Field    import Field
from RBK.Model.Functions import runQuery, createModel, isError
import RBK.Model.Functions as Func

class Database(QObject):
    errored = pyqtSignal(str)
    relayAddField = pyqtSignal(str, str)
    relayRemoveField = pyqtSignal(str, str)
    relayAddDataType = pyqtSignal(str)
    relayRemoveDataType = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        # Create a db w/ a list of the Games, versions and available Core Rulebooks
        self.models = {}
        self.db = QSqlDatabase().addDatabase('QSQLITE', 'db')
        self.dbcore = QSqlDatabase().addDatabase('QSQLITE', 'dbcore')
        self.rulebooks = QSqlDatabase().addDatabase('QSQLITE', 'rulebooks')
        self.rulebooks.setDatabaseName(':memory:')
        self.rulebooks.open()
        runQuery(self.rulebooks, "CREATE TABLE Rulebooks(GameName TEXT NOT NULL, GameVersion REAL NOT NULL, RulebookName TEXT PRIMARY KEY NOT NULL, Hash TEXT NOT NULL, Core TEXT NOT NULL, CoreRulebook TEXT, CoreRulebookHash TEXT)")
        runQuery(self.rulebooks, "CREATE TABLE ChangeLog(Table TEXT NOT NULL, Field TEXT NOT NULL, OldValue TEXT NOT NULL, NewValue TEXT NOT NULL)")
        iter = QDirIterator('Rulebooks/', ['*.rbk'], QDir.NoFilter, QDirIterator.Subdirectories)
        while(iter.hasNext()):
            rbk = iter.next()
            self.db.setDatabaseName(rbk)
            if not self.db.open():
                errored.emit('Error: Cannot open database: {0}'.format(self.db.lastError().text()))
            else:
                hasher = hashlib.sha256()
                with open(rbk, 'rb') as dbfile:
                    buffer = dbfile.read()
                    hasher.update(buffer)
                    hash = hasher.hexdigest()
                    print('{0}\t{1}'.format(rbk, hash))
                    dbfile.close()

                rbkquery = runQuery(self.db, 'SELECT * FROM Rulebook LIMIT 1')
                rbkinfo = rbkquery.getValue()[0]
                result = runQuery(self.rulebooks, ('INSERT INTO Rulebooks VALUES ("{0}", "{1}", "{2}", "{3}", "{4}", "{5}", "{6}")'.format(rbkinfo.value('GameName'), rbkinfo.value('GameVersion'), rbkinfo.value('RulebookName'), hash, rbkinfo.value('Core'), rbkinfo.value('CoreRulebook'), rbkinfo.value('CoreRulebookHash'))))
            self.db.close()
        self.models['rulebooks'] = QSqlTableModel(None, self.rulebooks)
        self.models['rulebooks'].setTable('Rulebooks')
        self.models['rulebooks'].select()


    def newRulebook(self, game, version, rbk, core, corerbk):
        # Create directory structures if they don't already exist
        if not os.path.exists('Rulebooks/{0}'.format(game)):
            os.makedirs('Rulebooks/{0}'.format(game))
            os.makedirs('Rulebooks/{0}/{1}'.format(game, version))
        elif not os.path.exists('Rulebooks/{0}/{1}'.format(game, version)):
            os.makedirs('Rulebooks/{0}/{1}'.format(game, version))
        filename = 'Rulebooks/{0}/{1}/{2}.rbk'.format(game, version, rbk)

        self.db.setDatabaseName(filename)

        if not self.db.open():
            errored.emit('Error:  Cannot open database: {0}'.format(self.lastError().text()))
            return False
        self.dataTypeModel = DataType(self)
        self.relayAddDataType.connect(self.dataTypeModel.addDataType)
        self.relayRemoveDataType.connect(self.dataTypeModel.removeDataType)
        self.fieldModel = Field(self)
        self.relayAddField.connect(self.fieldModel.addField)
        self.relayRemoveField.connect(self.fieldModel.removeField)

        if core=='False':
            corehash = self.getCoreRulebookHash(game, version, corerbk)
        else:
            corehash = 'None'

        runQuery(self.db, "CREATE TABLE Rulebook(GameName TEXT NOT NULL, GameVersion TEXT NOT NULL, RulebookName TEXT PRIMARY KEY NOT NULL, Core TEXT NOT NULL, CoreRulebook TEXT, CoreRulebookHash TEXT)")
        result = runQuery(self.db, 'INSERT INTO Rulebook (GameName, GameVersion, RulebookName, Core, CoreRulebook, CoreRulebookHash) VALUES ("{0}", "{1}", "{2}", "{3}", "{4}", "{5}")'.format(game, version, rbk, core, corerbk, corehash))

        if core=='True':
            self.addDataType('DataTypes')
            self.addDataType('Actions')
            self.addDataType('Currencies')
            self.addDataType('Character')

        for table in self.db.tables():
            createModel(self, table)

        if core=='False':
            print('Game = ' + game)
            print('Version = ' + version)
            print('CoreRBK = ' + corerbk)
            coreFilename = 'Rulebooks/{0}/{1}/{2}.rbk'.format(game, version, corerbk)
            self.openCoreRulebook(coreFilename)
        return True


    def openRulebook(self, filename):
        self.db.setDatabaseName(filename)
        print(filename)

        if not self.db.open():
            self.errored.emit('Error:  Cannot open database: {0}'.format(self.db.lastError().text()))
            return False

        for table in self.db.tables():
            createModel(self, table)

        if not self.isCore(self.db):
            result = runQuery(self.db, 'SELECT GameName, GameVersion, CoreRulebook FROM Rulebook LIMIT 1')
            if not isError(result):
                rbk = result.getValue()[0]
                game = rbk.value('GameName')
                version = rbk.value('GameVersion')
                corerbk = rbk.value('CoreRulebook')
                coreFilename = 'Rulebooks/{0}/{1}/{2}.rbk'.format(game, version, corerbk)
                self.openCoreRulebook(coreFilename)
            else:
                message = result.getValue()
                self.errored.emit(message)
                return False

        self.dataTypeModel = DataType(self)
        self.relayAddDataType.connect(self.dataTypeModel.addDataType)
        self.relayRemoveDataType.connect(self.dataTypeModel.removeDataType)
        self.dataTypeModel.errored.connect(self.errorReceived)

        self.fieldModel = Field(self)
        self.relayAddField.connect(self.fieldModel.addField)
        self.relayRemoveField.connect(self.fieldModel.removeField)
        self.fieldModel.errored.connect(self.errorReceived)

        return True


    def openCoreRulebook(self, filename):
        self.dbcore.setDatabaseName(filename)
        print('Core Rulebook = ' + filename)
        if not self.dbcore.open():
            self.errored.emit('Error:  Cannot open core database: {0}'.format(self.dbcore.lastError().text()))
            return False

        createModel(self, 'DataTypes')
        dtr = runQuery(self.dbcore, 'SELECT * FROM DataTypes')
        if not isError(dtr):
            print(dtr.getValue())
            datatypes = dtr.getValue()
            for dt in datatypes:
                s = 'Struct ' + dt.value(0)
                createModel(self, s)
                self.addDataType(dt.value(0))
                for r in range(self.models[s].rowCount()):
                    f = self.models[s].record(r).value(0)
                    if not ((f=='Name' or f=='Rulebook PDF Destination' or f=='Character Sheet Field')):
                        self.addField(dt.value(0), f)
            return True
        else:
            return False



    def closeRulebook(self):
        self.db.close()
        # store a copy of the rulebooks model, clear the models, and restore the rulebook model
        rbk = self.models['rulebooks']
        self.models.clear()
        self.models['rulebooks'] = rbk


    def getModels(db):
        return db.models


    def isCore(self, db):
        return Func.isCore(db)
        #return RBK.Model.Functions.isCore(db)


    @pyqtSlot(str, str)
    def addField(self, datatype, newfield):
        self.relayAddField.emit(datatype, newfield)

    @pyqtSlot(str, str)
    def removeField(self, datatype, fieldrow):
        self.relayRemoveField.emit(datatype, fieldrow)

    @pyqtSlot(str)
    def addDataType(self, text):
        self.relayAddDataType.emit(text)

    @pyqtSlot(str)
    def removeDataType(self, dt):
        self.relayRemoveDataType.emit(dt)

    @pyqtSlot(str)
    def errorReceived(self, message):
        self.errored.emit(message)

    def getGameList(self):
        gameList = []
        gameList.append('New Game...')
        gamesresult = runQuery(self.rulebooks, 'SELECT DISTINCT GameName From Rulebooks ORDER BY GameName ASC')
        if not isError(gamesresult):
            games = gamesresult.getValue()
            print('# Games: {0}'.format(str(len(games))))
            for game in games:
                print(game.value(0))
                gameList.append(game.value(0))
        print(gameList)
        return gameList


    def getVersionList(self, gamename):
        versionList = []
        versionList.append('New Version...')
        versionsresult = runQuery(self.rulebooks, 'SELECT DISTINCT GameVersion FROM Rulebooks WHERE GameName = "{0}" ORDER BY GameVersion DESC'.format(gamename))
        if not isError(versionsresult):
            versions = versionsresult.getValue()
            for version in versions:
                versionList.append(str(version.value(0)))
        return versionList


    def getCoreRulebooksList(self, gamename, version):
        coreBookList = []
        if not ((gamename=='New Game...') or (version=='New Version...')):
            coreBookResults = runQuery(self.rulebooks, 'SELECT DISTINCT RulebookName FROM Rulebooks WHERE GameName = "{0}" AND GameVersion = "{1}" And Core = "True" ORDER BY RulebookName ASC'.format(gamename, version))
            if not isError(coreBookResults):
                corebooks = coreBookResults.getValue()
                for corebook in corebooks:
                    coreBookList.append(str(corebook.value(0)))
        return coreBookList


    def getCoreRulebookHash(self, gamename, version, coreRulebook):
        corehash = None
        print('gamename: ' + gamename)
        print('version: ' + version)
        print('coreRulebook: ' + coreRulebook)
        hashresult = runQuery(self.rulebooks, 'SELECT Hash FROM Rulebooks WHERE GameName = "{0}" AND GameVersion = "{1}" And RulebookName = "{2}"'.format(gamename, version, coreRulebook))
        if not isError(hashresult):
            corehash = hashresult.getValue()[0].value('Hash')
            print('corehash: ' + corehash)
        else:
            print('Error in getCoreRulebookHash: ' + hashresult.getValue())
        return corehash
